package com.wnxy.sddfp.system.service.impl;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.wnxy.sddfp.client.bank.TradeLogApi;
import com.wnxy.sddfp.common.exception.Assert;
import com.wnxy.sddfp.common.model.dto.bank.BankCardAddDTO;
import com.wnxy.sddfp.common.model.dto.bank.CardBindDto;
import com.wnxy.sddfp.common.model.dto.bank.SignatureDto;
import com.wnxy.sddfp.common.model.dto.user.LoginDto;
import com.wnxy.sddfp.common.model.dto.user.UserRegisterDto;
import com.wnxy.sddfp.common.model.vo.LoginVo;
import com.wnxy.sddfp.common.result.enums.impl.BusinessCode;
import com.wnxy.sddfp.common.utils.JwtTemplate;
import com.wnxy.sddfp.common.utils.TokenUtils;
import com.wnxy.sddfp.system.entity.User;
import com.wnxy.sddfp.system.entity.UserCard;
import com.wnxy.sddfp.system.entity.UserDetail;
import com.wnxy.sddfp.system.mapper.UserMapper;
import com.wnxy.sddfp.system.service.IUserCardService;
import com.wnxy.sddfp.system.service.IUserDetailService;
import com.wnxy.sddfp.system.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author yellow docker
 * @since 2024-04-12
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private JwtTemplate jwtTemplate;

    @Autowired
    private IUserCardService userCardService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private TradeLogApi tradeLogApi;

    /**
     * 方法的实现
     * @param loginDto
     * @return
     *
     *  1、密码登录        pwdLogin
     *  2、手机短信登录    phoneLogin
     *
     */
    @Override
    public LoginVo login(LoginDto loginDto) {
        return loginDto.getIsPhoneLogin() ? phoneLogin(loginDto) : pwdLogin(loginDto);
    }

    /**
 *      * 用户绑卡：
 *      * 1、从token中获取userId
 *      * 2、校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
 *      * 3、如果未绑卡，保存保卡信息到u_user_card表
 *      * 4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中
 *      * 5、生成请求流水号，用来访问托管代理，并更新u_user_detail表中流水号字段REQ_CODE
 *      * 6、远程访问托管代理服务（暂不实现）
     * @param cardBindDto
     * @return
     */
    @Override
    public SignatureDto addBankAccount(CardBindDto cardBindDto) {
        
        //1、从token中获取userId 
        String userId = TokenUtils.getTokenUserId();
        System.out.println("userId = " + userId);

        // 2、校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
        UserDetail userDetail = userDetailService.getOne(Wrappers.lambdaQuery(UserDetail.class).eq(UserDetail::getId, userId));
        //getCardBindStatus 如果是true  抛出以绑卡的 异常
        Assert.error(userDetail.getCardBindStatus(),BusinessCode.USER_CARD_ALREADY_BINDED);

        //3、如果未绑卡，保存保卡信息到u_user_card表
        UserCard userCard = new UserCard();
        // 把字符串转成long 类型
        userCard.setUserId(Long.valueOf(userId));
        //卡号
        userCard.setCardNo(cardBindDto.getCardNo());
//        //获取手机号码 存储到 userCard中 （生成token的时候 把phone存储到token中）
//        User user = getOne(
//                Wrappers.lambdaQuery(User.class).eq(User::getId, userId)
//        );
        userCard.setPhone(userDetail.getPhone());

        userCard.setStatus(false);
        userCardService.save(userCard);

        // 4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中
        update(
                Wrappers.lambdaUpdate(User.class).eq(User::getId,userId)
                        .set(User::getRealName,cardBindDto.getRealName())
                        .set(User::getIdentityCode,cardBindDto.getIdentityCode())
        );

        //5、生成请求流水号，用来访问托管代理，并更新u_user_detail表中流水号字段REQ_CODE
        String reqCode = "REQ" + UUID.randomUUID().toString().substring(10);

        userDetailService.update(
                Wrappers.lambdaUpdate(UserDetail.class)
                        .eq(UserDetail::getId,userId)
                        .set(UserDetail::getReqCode,reqCode)
        );

        //6、进行远程调用
        //远程接口地址：http://localhost:9030/trade-log/bank/access/card
        //请求方式：post
        //请求参数：BankCardAddDTO（reqCode、userId）
        //返回数据：SignatureDto
        String url = "http://localhost:9030/trade-log/bank/access/card";
        BankCardAddDTO bankCardAddDTO = new BankCardAddDTO();
        bankCardAddDTO.setReqCode(reqCode);
        bankCardAddDTO.setUserId(Long.valueOf(userId));
        // 执行远程调用
//        SignatureDto signatureDto =restPRC(url, bankCardAddDTO);

        //使用openfeigin 进行远程调用
        SignatureDto signatureDto = tradeLogApi.proxyBindCardLog(bankCardAddDTO);
        System.out.println("signatureDto = " + signatureDto);

        return signatureDto;
    }

    /**
     * restTemplate 进行远程调用
     * @param url
     * @param bankCardAddDTO
     * @return
     */
    @Nullable
    private SignatureDto restPRC(String url, BankCardAddDTO bankCardAddDTO) {
        //restTemplate.postForEntity 发送post请求
        ResponseEntity<SignatureDto> responseEntity =
                restTemplate.postForEntity(url, bankCardAddDTO, SignatureDto.class);
        //获取返回结果的实体
        SignatureDto signatureDto = responseEntity.getBody();
        return signatureDto;
    }

    /**
     *  手机短信登录
     * @param loginDto
     * @return
     *   1、通过手机获取验证码
     *   2、通过手机号码进行登录
     *
     */
    private LoginVo phoneLogin(LoginDto loginDto) {

        //01 判断 手机号码是否存在 判断验证码
        //验证码
        String secret = loginDto.getLoginSecret();

        String key = "SMS:" + loginDto.getLoginName();
        String redisCode = stringRedisTemplate.opsForValue().get(key);

        if (StringUtils.isEmpty(redisCode)){
            Assert.error(BusinessCode.USER_VERIFY_CODE_ERROR);
        }
        //一致
        if (secret.equals(redisCode)){
            LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(User.class)
                    .eq(User::getPhone, loginDto.getLoginName());
            User user = getOne(queryWrapper);

            //   如果手机号码、第一次进行登录
            //   自动注册
            if (user == null){
                //第一次
                UserRegisterDto userRegisterDto = new UserRegisterDto();
                //用户名随机
                userRegisterDto.setUserName(RandomUtil.randomString(8));
                //固定密码 123456
                userRegisterDto.setPwd(passwordEncoder.encode("123456"));
                userRegisterDto.setPhone(loginDto.getLoginName());

                //注册
                saveUser(userRegisterDto);
            }
            user = getOne(queryWrapper);
            //02 登录后 返回用户
            return getLoginVo(user);
        }else{
            Assert.error(BusinessCode.USER_VERIFY_CODE_ERROR);
        }
        return null;
    }

    /**
     *  把用户转成 loginVo
     * @param user
     * @return
     */
    private LoginVo getLoginVo(User user) {
        LoginVo loginVo = new LoginVo();
        loginVo.setId(user.getId());
        loginVo.setUserName(user.getUserName());
        loginVo.setPhone(user.getPhone());

        //使用jwt 工具类生成一个token  导入工具类就可以
        HashMap<String, Object> payLaod = new HashMap<>();
        payLaod.put("userId",user.getId().toString());
        payLaod.put("phone",user.getPhone());

        String token = jwtTemplate.createToken(payLaod);
        loginVo.setToken(token);

        //redis中需要存储

        String key = "TOKEN:" + user.getId();
        stringRedisTemplate.opsForValue().set(
                key,
                token,
                30,
                TimeUnit.DAYS
        );

        return loginVo;
    }

    /**
     *  密码登录
     * @param loginDto
     * @return
     */
    private LoginVo pwdLogin(LoginDto loginDto) {
        // 1、通过 用户名词查询
        User user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getUserName, loginDto.getLoginName()));

        //用户为空抛出异常
        Assert.error(user,BusinessCode.USER_NOT_EXIST);
        // 2、密码比对
        // matches 密码比对 (参数1： 登录传递过来的明文密码  参数2: 数据库中存储的加密后的密码)
        boolean matches = passwordEncoder.matches(loginDto.getLoginSecret(), user.getPwd());
        //密码 不对
        if (!matches){
            //抛异常
            Assert.error(BusinessCode.USER_LOGIN_ERROR);
        }
        //3、返回用户登录信息

        //使用jwt 工具类生成一个token  导入工具类就可以
        HashMap<String, Object> payLaod = new HashMap<>();
        payLaod.put("userId",user.getId().toString());
        payLaod.put("phone",user.getPhone());

        String token = jwtTemplate.createToken(payLaod);

        //redis中需要存储
        String key = "TOKEN:" + user.getId();
        stringRedisTemplate.opsForValue().set(
                key,
                token,
                30,
                TimeUnit.DAYS
        );

        if (matches){
            return   LoginVo.builder()
                    .userName(user.getUserName())
                    .id(user.getId())
                    .phone(user.getPhone())
                    .token(token)
                    .build();
        }
        return null;
    }

    @Autowired
    private IUserDetailService userDetailService;

    @Autowired
    private PasswordEncoder passwordEncoder;
    /**
     * 注册代码实现
     * @param userRegisterDto
     *    1、判断手机号码 或者用户名不能重复
     *    2、saveUser
     *    3、saveUserDetail
     *
     *  这个业务有多表操作:开启 事务
     *     @Transactional  开启事务注解
     *         propagation  事务的传播行为
     *             Propagation.REQUIRED  当前的方法有事务直接使用 没有事务 创建一个事务 (默认)
     *         rollbackFor  事务回滚的异常
     *             默认是 RuntimeException
     *             rollbackFor = Exception.class  （所有的异常都会回滚事务）
     *    这里为什么需要给事务?
     *      1、这个业务中 存在多表的操作 要不都成功 要不都失败
     *           必须添加事务
     *      2、添加事务 使用  @Transactional 注解就可以
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    @Override
    public void register(UserRegisterDto userRegisterDto) {

        //01 判断手机号码 或者用户名不能重复
        // 判断手机号码 不能重复注册
        LambdaQueryWrapper<User> phoneWrapper = Wrappers.lambdaQuery(User.class)
                .eq(User::getPhone, userRegisterDto.getPhone());
        User one = getOne(phoneWrapper);
        Assert.error(one != null, BusinessCode.USER_PHONE);

        //判断 用户名是否重复注册
        User userName = getOne(Wrappers.lambdaQuery(User.class).eq(User::getUserName, userRegisterDto.getUserName()));
        Assert.error(userName != null, BusinessCode.USER_EXISTED);

        //02 注册 就是往 db中添加数据
        saveUser(userRegisterDto);  // ctrl + alt + m 代码块的抽取
    }

    /**
     *  保存用户
     * @param userRegisterDto
     */
    private void saveUser(UserRegisterDto userRegisterDto) {
        //  u_user 、u_user_detail 中添加数据
        User user = new User();

        user.setPhone(userRegisterDto.getPhone());
        user.setUserName(userRegisterDto.getUserName());

        //存储在db中的密码需要加密
        user.setPwd(passwordEncoder.encode(userRegisterDto.getPwd()));
        //1  是个人  2 机构
        //user.setUserType(UserType.USER_TYPE);
        user.setUserType("1");
        //状态 1 正常  0 作废
        user.setUserStatus(true);

        save(user);

        UserDetail userDetail = new UserDetail();
        //给用户的id
        userDetail.setId(user.getId());
        userDetail.setUserName(userRegisterDto.getUserName());
        userDetail.setPhone(userRegisterDto.getPhone());
        userDetail.setBorrowMoney(new BigDecimal("0"));
        userDetail.setUserCode("UC_" + UUID.randomUUID().toString().substring(0,10));
        userDetail.setCardBindStatus(false);

        userDetailService.save(userDetail);
    }

}
